using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Andshev.OrmTools.Linq2Query.Extensions;

//paging: with tbl as (select <COLUMNS>, ROW_NUMBER() over (order by Id asc) as Row from <TABLE>) select <COLUMNS> from tbl where Row between 1 and 5
// TODO prohibit selecting foreign entities in Select method (message: Use Join method instead)

namespace Andshev.OrmTools.Linq2Query.Queries
{
    public class SelectQuery<T> : QueryBase
    {
        private string _selectClause;
        private string _fromClause;
        private readonly List<string> _innerJoinClauses;
        private string _whereClause;
        private int _aliasCounter = 0;

        public SelectQuery(OrmContext context)
            : base(context)
        {
            _innerJoinClauses = new List<string>();
        }

        public SelectQuery<T> Select()
        {
            _selectClause = "{0} *".FormatWith(Constants.Sql.Select);
            GenerateFromClause();

            return this;
        }

        public SelectQuery<T> Select(Expression<Func<T, object[]>> expression)
        {
            _selectClause = "{0} {1}".FormatWith(Constants.Sql.Select, expression.ToSqlColumns());
            GenerateFromClause();

            return this;
        }

        private void Join<TJoined>(string kindOfJoin, Expression<Func<T, object>> expression)
        {
            string joinedTableName = Context.GetTableName<TJoined>();
            string joiningPropertyName = expression.ToSqlColumns();
            ForeignMapping foreignMapping = Context.GetForeignMapping(typeof(T), joiningPropertyName);
            string joiningColumnName = foreignMapping.MasterColumnName;
            string joinedForeignColumnName = foreignMapping.ForeignColumnName;

            _aliasCounter++;
            _innerJoinClauses.Add("{0} {1} t{2} on t{2}.{3}=t.{4}".FormatWith(
                                      kindOfJoin,
                                      joinedTableName,
                                      _aliasCounter,
                                      joinedForeignColumnName,
                                      joiningColumnName));
        }

        public SelectQuery<T> InnerJoin<TJoined>(Expression<Func<T, object>> expression)
        {
            Join<TJoined>(Constants.Sql.InnerJoin, expression);

            return this;
        }

        public SelectQuery<T> LeftJoin<TJoined>(Expression<Func<T, object>> expression)
        {
            Join<TJoined>(Constants.Sql.LeftJoin, expression);

            return this;
        }

        public SelectQuery<T> Where(Expression<Func<T, bool>> expression)
        {
            _whereClause = "{0} {1}".FormatWith(Constants.Sql.Where, expression.ToSqlCondition());
            
            return this;
        }

        private void GenerateFromClause()
        {
            string joiningTableName = Context.GetTableName<T>();
            _fromClause = "{0} {1} t".FormatWith(Constants.Sql.From, joiningTableName);
        }

        public override string ToString()
        {
            return string
                .Join(" ", new string[]
                               {
                                   _selectClause,
                                   _fromClause,
                                   string.Join(" ", _innerJoinClauses.ToArray()),
                                   _whereClause
                               })
                .Trim();
        }
    }
}